iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0

GLib是用C實作的高階程式庫,目標是易用及容易開發應用程式,而GStreamer是基於GLib實作的多媒體串流框架,所以在我們深入GStreamer之前,我認為先認識GLib是有必要的。

C標準的不足

C誕生到現在已經超過50年了,輕量、語法簡單、低限度的抽象,這些都是C的優勢,而當提到C的標準庫會發現C99(1999年)是一個很常使用的標準庫版本。其實C的標準庫定義已經到了C23,但幾乎所有應用都停留在C99,為什麼會這樣?

大家可以去查cppreference這個網站,往下滑會有C標準庫的資訊,基本上除了C11標準化了thread和atomic我覺得挺有用地之外,其他新增的內容少之又少且幫助不大。再來是更殘酷的現實,「編譯器支援」(compiler support),打開網站一看發現MSVC那邊紅紅一片,當然我知道很多人很唾棄微軟,但那也僅限於你沒有接觸到不得不用Windows的產業,當你得使用時這就是物理上的硬限制,我曾經發現有atomic可以使用很開心,結果編譯下去直接錯誤顯示不支援。

要先提到標準庫是因為C相較於已經魔化的C++而言是個非常單純的語言,很多操作是要直接調用OS的API的,例如C11之前必須得調用OS API才能寫多執行緒。C又不像很多高階語言一樣內建Container型別(List、HashTable...),GLib的誕生就是為了解決這些標準庫缺乏的內容,以及增加更多完成一個應用程式需要的特性。

高階特性

GLib提供了以下高階特性讓開發應用程式更容易

String Utilities

常用的字串操作功能,例如分割(split)、字串陣列(strv),因為C沒有真正字串型別,所以GLib提供了不少函式簡化字串操作的需求。

Error Reporting

C沒有標準的錯誤回報型別,往往都是用enum或int來代表錯誤碼,再提供一個錯誤碼轉字串的函式轉為人可閱讀的錯誤資訊。可是這衍生了一個問題就是錯誤資訊不夠明確。舉一個常見的案例Invalid Argument,引數到底錯在哪?數值不在範圍內?數值衝突?引數無效?通常遇到都得從文件描述的原因去排查。

另一個常見的問題是每個程式庫都會定義一套自己的錯誤碼,但當內部有使用到System API或是第三方API時,通常會用一個錯誤碼代表Runtime Error,就是你沒有預料到的錯誤發生了。如果沒有搞清楚相依程式庫的所有錯誤碼會在何時發生,很容易遺失錯誤資訊,導致排查的困難。

GLib提供了GError這個型別帶有Error Domain、Error Code、Error Message等資訊

  • Error Domain 唯一的識別符,代表錯誤來自哪個模組,可以不用層層包裝錯誤碼
  • Error Code 模組定義的錯誤碼,可以避免用int涵蓋全部造成的錯誤碼衝突
  • Error Message 錯誤發生的原因,這是動態產生的字串,可以描述發生當下的詳細問題,避免像靜態文字那樣不明確的問題

Logging

很多高階語言都已經有內建的Logging程式庫或是介面,模組之間可以共用同一套標準,但C沒有這項特性,不同的Logger定義、偷偷印在stdout、關不掉logging,這些事情經常發生,GLib內建了Logging程式庫和Macro來解決上述的痛點。

Event Loop

現在大多數的熱門語言都有提供非同步設計模式,並透過關鍵字支援,但有深入研究過的人會知道編譯器其實幫你生成了很多程式碼去完成這個操作。如果要做到「通用的」非同步設計,只能仰賴Event Pooling,至少在類Unix的平台上都是如此(多數人討厭的Windows反而可以不用Pooling),為什麼會這樣呢?

當我透過System API發起了一個非同步操作,函式都會返回一個物件(任意型式)讓你可以透過這個物件詢問非同步操作的狀態,反覆詢問直到完成後,再透過這個物件取得非同步操作的結果,有興趣可以去查詢epoll這個函式及System API是怎麼實現非同步I/O。

GLib提供了GMainLoop及GSource,Loop負責Event Pooling,Source就是前面提到的物件(用來詢問狀態),負責讓Loop輪詢,Source可以設定callback function,在事件完成後會由Loop排程呼叫。此外,大多數基於GLib開發的程式庫都用到了Event Loop的功能,所以你要讓程式庫能正常跑起來的話,必須得在主程式讓一個GMainLoop跑起來。

Container

現代的編譯語言在容器型別支援都仰賴Metaprograming(元編程),就是先宣告一個中繼型別把容器的邏輯實作完,引入真正型別後再透過編譯器去產生實際程式碼,常見的範例就是C++的std::array、std::vector、std::unordered_map等容器型別。C沒有提供元編程的特性,只能仰賴Macro去彌補,但可讀性差且除錯困難,大部分的程式庫都是透過指標void*搭配element_size來實作,GLib也是如此,提供了GArray(連續空間)、GList(Linked List)、GHashTable,讓我們更容易實現應用需要的邏輯。

GObject

物件導向編程(OOP)主導了程式設計好一段時間,至今仍然是如此,今天我們就不談論OOP的好壞,帶大家認識一下GObject特性。

不少人直覺會認為C沒有物件導向,語言本身確實沒有內建物件導向的特性,但是物件導向的特性是可以透過語言實現的,只是得實作一套完整的框架才能做到,GObject就是GLib提供的物件導向框架。

框架提供了繼承(Inheritance)、介面(Interface)、虛擬函式(Virtual Function)等OOP特性,基本上可以滿足大部分的情境。至於封裝性則忽略,GObject不採取透明型別(Opaque Type),而是直接公開結構的宣告,把物件擁有的資料及虛擬函式都宣告在成員內,仰賴約束開發者得透過標準的API去操作物件。

最後就是Resource Management,GObject採用參照計數(Reference Counting)去管理物件的生命週期,框架為每個物件定義了<objec>_init、<object>_dispose、<object>_finalize等函式去處理資源分配,框架會負責管理參照計數,在計數歸0時自動調用清理函式。

結語

GLib的設計圍繞在方便使用,因此大量使用Heap Allocation,許多型別也都是實例化後回傳指標,GObject的操作仰賴大量的字串及文件描述,所以如果你是C或C++的開發者,初次接觸時可能要適應一下它的開發流程。


上一篇
[Day 15] 細看影片編碼
下一篇
[Day 17] 認識GStreamer
系列文
深入認識DeepStream,不只是停在執行範例 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言